<?php

$_D = __DIR__ . DIRECTORY_SEPARATOR;
require_once $_D . 'config.inc.php';
//require_once 'bs/DataObject.php';

//所有需要Bo为参数的，最好使用Bo->

function pdo( $dbUser = G_DB_USER,
    $passwd = G_DB_PWD,
    $dbName = G_DB_NAME,
    $dbHost = G_DB_HOST,
    $port = G_DB_PORT,
    $charset = G_DB_CHARSET,
    $pconnect = G_DB_PCONNECT){

    static $instance = null;
    if(null != $instance) return $instance;

    $instance=new LinhePDO( $dbUser,        $passwd ,        $dbName ,        $dbHost ,
        $port ,        $charset ,        $pconnect );
    return $instance;
}


class LinhePDO
{
    /**
     * @var PDO
     */
    private $pdo; //指向系统的指针
   // private $db;
   // private $cfg;
    /**
     * @var PDOStatement
     */
    private  $statement; //PDOStatement
   // private $errors = array();

    public function __construct(
        $dbUser = G_DB_USER,
        $passwd = G_DB_PWD,
        $dbName = G_DB_NAME,
        $dbHost = G_DB_HOST,
        $port = G_DB_PORT,
        $charset = G_DB_CHARSET,
        $pconnect = G_DB_PCONNECT
    )
    {
        // $cfg = $G_CONFIG['db'];
        $dsn = "mysql:dbname=$dbName;host=$dbHost;port=$port";
        $options = array(PDO::ATTR_PERSISTENT => $pconnect);
        $this->pdo = new PDO($dsn, $dbUser, $passwd, $options);
        assert($this->pdo != null);
        $sql = "SET NAMES '$charset';";
        $this->exec($sql);
        $this->exec("SET sql_mode='';");
        // $this->cfg = $fg;
    }

    function exec($query)
    {
        return $this->pdo->exec($query);
    }

    /**
     * @param string $sql
     * @param array $params
     * @return bool|mixed
     */
    //操作： 修改statement；
    /**
     * @return PDOStatement
     */
    public function getStatement()
    {
        return $this->statement;
    }

    /**
     * @分页方法
     * $sql sql语句
     * @当前页码  当前第几页 传$_GET['page']
     * @显示条数 每页展示多少条
     * @url参数 传$_GET即可
     */
    public function page($sql,$当前页码,$显示条数,$参数,$order=null) {
        //判断是否是第一次访问
        $当前页码 = Empty($当前页码) ? 1 :$当前页码;

        //首先查询总条数
        $总数sql = $sql;
        $查询总数据 = pdo()->query_fetch_all($总数sql);
        $总数 = count($查询总数据);
        //算出总页数
        $总页数 = Empty($显示条数) ? 1 :ceil($总数/$显示条数);
        //对异常数据的处理
        if(intval($当前页码)<1) {
            $当前页码 = 1;
        }else if(intval($当前页码)>$总页数) {
            $当前页码 = $总页数;
        }

        //显示条数为0 取全部条数
        if($显示条数) {
            $limit =" ".$order. ' limit '.($当前页码-1)*$显示条数 .','.$显示条数;
        }else {
            $limit = '';
        }
        //执行最后查询
        $查询sql = $sql.$limit;
        $查询结果 = $this->query_fetch_all($查询sql);

        logger()->logDebug('查询语句:'.$查询sql);

        //生成分页模板
        $显示模板 = Empty($显示条数) ? '' : $this->page_module($总数,$当前页码,$显示条数,$参数);
        return array('result' =>$查询结果 ,'show'=>$显示模板);
    }


    public function page_module($总数,$当前页码,$显示条数,$参数) {
        $总页数 = ceil($总数/$显示条数);
        $上一页 = $参数;
        $下一页 = $参数;
        $下一页['page'] = intval($当前页码) +1;
        $上一页['page'] = intval($当前页码) -1;
        $下一页1 = '';
        $上一页1 = '';

        $首页 = $参数;
        $尾页 = $参数;
        $首页['page'] = 1;
        $尾页['page'] = $总页数;
        $首页1 = '';
        $尾页1 = '';
        foreach($上一页  as $key=>$value) {
            $上一页1 .= $key.'='.$value.'&';
        }
        foreach($下一页  as $key=>$value) {
            $下一页1 .= $key.'='.$value.'&';
        }

        foreach($首页  as $key=>$value) {
            $首页1 .= $key.'='.$value.'&';
        }
        foreach($尾页  as $key=>$value) {
            $尾页1 .= $key.'='.$value.'&';
        }
        $上一页1 = rtrim($上一页1,'&');
        $下一页1 = rtrim($下一页1,'&');
        $首页1 = rtrim($首页1,'&');
        $尾页1 = rtrim($尾页1,'&');
        $str= "<div class='pages'>";
        if($当前页码 < $总页数) $str.="<span><a  class='next_page' href='http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME'].'?'.$下一页1."'> 下一页 </a></span>";
        if($当前页码 > 1) $str.="<span><a class='back_page' href='http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME'].'?'.$上一页1."'> < </a></span>";
        // $str.= "<span></span>";

        $str.= "
<div class='page_tips'>当前". $当前页码 ."页/共".$总页数."页</div>
 ";


        return $str;


    }



    public function page_倒排正显示($sql,$当前页码,$显示条数,$参数,$order=null) {
        //判断是否是第一次访问
        $当前页码 = Empty($当前页码) ? 1 :$当前页码;

        //首先查询总条数
        $总数sql = $sql;
        $查询总数据 = pdo()->query_fetch_all($总数sql);

        $总数 = count($查询总数据);

        //算出总页数
        $总页数 = Empty($显示条数) ? 1 :ceil($总数/$显示条数);

        //对异常数据的处理
        if(intval($当前页码)<1) {
            $当前页码 = 1;
        }else if(intval($当前页码)>$总页数) {
            $当前页码 = $总页数;
        }
         $初始查询 =(($总数-($当前页码*$显示条数))<0)?0 :($总数-($当前页码*$显示条数));
        $显示条数new =(($总数-($当前页码*$显示条数))<0)?($总数-($当前页码-1)*$显示条数) :$显示条数;
        //显示条数为0 取全部条数
        if($显示条数new) {
            $limit =" ".$order. ' limit '.$初始查询 .','.$显示条数new;

        }else {
            $limit = '';
        }
        //执行最后查询
        $查询sql = $sql.$limit;

        $查询结果 = $this->query_fetch_all($查询sql);

        logger()->logDebug('查询语句:'.$查询sql);

        //生成分页模板
        $显示模板 = Empty($显示条数) ? '' : $this->page_module_倒排正显示($总数,$当前页码,$显示条数,$参数);
        return array('result' =>$查询结果 ,'show'=>$显示模板);
    }



    public function page_module_倒排正显示($总数,$当前页码,$显示条数,$参数) {

        $总页数 = ceil($总数/$显示条数);
        $上一页 = $参数;
        $下一页 = $参数;
        $下一页['page'] = intval($当前页码) +1;
        $上一页['page'] = intval($当前页码) -1;
        $下一页1 = '';
        $上一页1 = '';

        $首页 = $参数;
        $尾页 = $参数;
        $首页['page'] = 1;
        $尾页['page'] = $总页数;
        $首页1 = '';
        $尾页1 = '';
        foreach($上一页  as $key=>$value) {
            $上一页1 .= $key.'='.$value.'&';
        }
        foreach($下一页  as $key=>$value) {
            $下一页1 .= $key.'='.$value.'&';
        }

        foreach($首页  as $key=>$value) {
            $首页1 .= $key.'='.$value.'&';
        }
        foreach($尾页  as $key=>$value) {
            $尾页1 .= $key.'='.$value.'&';
        }
        $上一页1 = rtrim($上一页1,'&');
        $下一页1 = rtrim($下一页1,'&');
        $首页1 = rtrim($首页1,'&');
        $尾页1 = rtrim($尾页1,'&');
        $str= "<div class='pages'>";

        if($当前页码 < $总页数) $str.="<span><a  class='next_page' href='http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME'].'?'.$下一页1."'> 下一页 </a></span>";
        if($当前页码 > 1) $str.="<span><a class='back_page' href='http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME'].'?'.$上一页1."'> < </a></span>";
        // $str.= "<span></span>";

        $str.= "
<div class='page_tips'>当前". $当前页码 ."页/共".$总页数."页</div>
 ";


        return $str;


    }




    /**
     * 查询单表，单条记录，结果以BO形式返回
     * @param DataObject $doClass //$do被返回值修改
     * @param [] $wheres
     * @return bool|DataObject
     */
    public function selectBO(DataObject $doClass, $wheres,$whereStatement=null)
    {
        if(empty($whereStatement)) return $this->selectBO__1($doClass,$wheres);

        $sql = " SELECT * FROM" . " " . $doClass->tableName()." WHERE " .$whereStatement;
        $values = array_values($wheres);
        $array = $this->selects($sql,$values);
        if ($array) {   //有且仅有一条记录
            $doClass->set属性数组($array[0]);
            $doClass->has_loaded = true;

            return $doClass;
        }

        return false; //没有数据
    }

    /**
     * 查询单表，单条记录，结果以BO形式返回
     * @param DataObject $doClass //$do被返回值修改
     * @param [] $wheres
     * @return bool|DataObject
     */
    private function selectBO__1(DataObject $doClass, $wheres)
    {
        $sql = " SELECT * FROM" . " " . $doClass->tableName();
        $array = $this->selectOne($sql, $wheres);
        if ($array) {   //有且仅有一条记录
            $doClass->set属性数组($array);
            $doClass->has_loaded = true;

            return $doClass;
        }

        return false; //没有数据
    }
    public function checkRecordExist($vs表名,array $va条件,$vs逻辑关系='AND'){
        $sql = " SELECT `id` FROM" .' '. $vs表名 ;
        $sel = $this->to_sql($sql, $va条件, 'WHERE', $vs逻辑关系);
        $conditions = array_values($va条件);
        $ret = $this->query_fetch($sel, $conditions);
        if ($ret)    //记录存在
            return true;
        else    //记录不存在
            return false;
    }
    /**
     * 查询一条记录
     * @param string $sql 不用写where "select * from tableName"
     * @param array $wheres $k=>v, 函数只用于=条件
     * @param int $style
     * @param string $glue 只能全部是and或or
     * @return bool|array    返回列名索引的数组
     */
    public function selectOne($sql, array $wheres=array(), $glue = 'AND', $style = PDO::FETCH_ASSOC)
    {
        $sel = $this->to_sql($sql, $wheres, 'WHERE', $glue);
        $conditions = array_values($wheres);
        $rtn = $this->query_fetch($sel, $conditions, $style);
        if ($rtn == false)  //无结果
            return false;

        if ($this->statement->fetch()) {
            return false;

        } //多个结果

        return $rtn; //一条结果

    }

    /**
     * 组装sql，将sql增加 ？占位符
     * @param string $sql
     * @param array $params $k=>v
     * @param string $where where set ''.....
     * @param string $glue
     * @return bool|string
     */
    public function to_sql($sql, array $params, $where = 'WHERE', $glue = 'AND')
    {
        $fields = $this->_placeholders2($params, $glue);

        return $sql . (($fields != '') ? " $where " . $fields : ''); //+where 条件
    }

    private function  _placeholders2(array $params, $glue)
    {
        $fields = '';
        $split = ' ';// . $glue . ' ';
        //$and = false;
        foreach ($params as $k => $v) {
            $fields .= $split . "$k = ?";    //占位符
            $split = ' ' . $glue . ' ';
        }

        return $fields;
    }

    /**
     * 该函数组合了pdo的prepare，execute和fetch，方便使用
     * @param string $sql
     * @param array $params
     * @param int $style
     * @return bool|mixed
     * FETCH_ASSOC 返回列名索引的数组
     *其他style详细说明 http://php.net/manual/en/pdostatement.fetch.php
     */
    public function query_fetch($sql, array $params = array(), $style = PDO::FETCH_ASSOC)
    {
        $statement = $this->prepare_execute($sql, $params);
        if (false == $statement) {
            return false;
        }

        return $statement->fetch($style);
    }


    public function prepare_execute($sql, $params)
    {
        $this->statement = null;
        $statement = $this->prepare($sql);
        //Linhe::Loger()->logDebug2($sql,__LINE__);
        if (!is_object($statement)) {
            // $this->debug(false, array('sql' => $sql, 'error' => array('', '-1', '当前连接数据库用户没有执行该条语句的权限，请检查mysql权限配置')));
            return false;
        }
        $result = $statement->execute($params);
        if (false == $result) {
            return false;
        }
        $this->statement = $statement;

        return $statement;
    }

    function prepare($query, array $conditions = array())
    {
        return $this->pdo->prepare($query, $conditions);
    }

    /**
     * 查询单表，多条记录，结果以BO数组形式返回
     * @param DataObject $doClass
     * @param array $whereParams
     * @param string $whereStatement
     * @param string $orderbyStatement
     * @param array $orderbyParams
     * @param int $limitIndex
     * @param int $limitCount
     * @return DataObject[]|bool
     */
    public function selectBOs(
        DataObject $doClass,
        $whereParams = array(),
        $whereStatement = '',
        $orderbyStatement = '',
        $orderbyParams = array(),
        $limitIndex = 0,
        $limitCount=1000)
    {
        $doClass->has_loaded = false;
        $sql =  " SELECT * FROM"." ". $doClass->tableName();

        if (!empty($whereStatement)) {
            $sql .= " WHERE " . $whereStatement;
        } else {
            $sql = $this->to_sql($sql, $whereParams);
        }
        $sql .= " " . $orderbyStatement;
        if($limitCount)
        {
            $sql .= " LIMIT $limitIndex,$limitCount ";
        }
        $pars = array_merge(array_values($whereParams), $orderbyParams);
        $array = $this->selects($sql, $pars);

        if ($array) {
            $dos = array();

            foreach ($array as $record) {
                $rcddo = clone($doClass);
                $rcddo->set属性数组($record);
                $rcddo->has_loaded = true;
                $dos[] = $rcddo;
            }

            return $dos;
        }
        return false; //没有数据
    }



    public function selectBOsPart(
        DataObject $doClass,
        $columns,
        $whereParams = array(),
        $whereStatement = '',
        $orderbyStatement = '',
        $orderbyParams = array(),
        $limitIndex = 0,
        $limitCount=100)
    {
        $doClass->has_loaded = false;
        $sql =  " SELECT $columns FROM ". $doClass->tableName();

        if (!empty($whereStatement)) {
            $sql .= " WHERE " . $whereStatement;
        } else {
            $sql = $this->to_sql($sql, $whereParams);
        }

        if(empty($orderbyStatement))
            $sql.=" ORDER BY `id` ";
        else
            $sql .= " " . $orderbyStatement;
        $sql .= " LIMIT $limitIndex,$limitCount ";

        $pars = array_merge(array_values($whereParams), $orderbyParams);
        $array = $this->selects($sql, $pars);

        if ($array) {
            $dos = array();

            foreach ($array as $record) {
                $rcddo = clone($doClass);
                $rcddo->set属性数组($record);
                $rcddo->has_loaded = true;
                $dos[] = $rcddo;
            }

            return $dos;
        }
        return false; //没有数据
    }
    /**
     * 查询，自由式查询，查询中的参数为？，参数值由params数组依次赋值。sql语句中的？数量必须与params数量相同，并且顺序一致。
     * @param $sql
     * @param array $params
     * @return bool|array[]
     */
    public function selects($sql, $params = array())
    {
        //$sel = $this->to_sql($sql,$wheres,'WHERE',$glue);
        if (substr_count($sql, '?') != count($params)) {
            return false;
        }
        $conditions = array_values($params);

        return $this->query_fetch_all($sql, $conditions);
    }

    public function query_fetch_all($sql, array $params = array(), $style = PDO::FETCH_ASSOC)
    {
        $statement = $this->prepare_execute($sql, $params);
        if (false == $statement) {
            return false;
        }

        return $statement->fetchAll($style);
    }

    /**
     * 查询，返回多条记录，查询sql中不包含参数，参数由$where参数赋值
     * @param $sql
     * @param array $wheres
     * @param string $glue
     * @return bool|array      * 返回列名索引的二维数组
     */
    public function selectAll($sql, $wheres = array(), $glue = 'AND')
    {
        $sel = $this->to_sql($sql, $wheres, 'WHERE', $glue);
        $conditions = array_values($wheres);

        return $this->query_fetch_all($sel, $conditions);
    }

    /**
     * @param string $tableName
     * @param array $columns
     * @param array $wheres
     * @param string $glue
     * @return bool|array[]
     */
    public function selectColumns( $tableName,array $columns,$wheres=array(),$glue='AND'){
        $sql = "SELECT ".implode(',',$columns)." FROM $tableName ";
        $sql = $this->to_sql($sql,$wheres,'WHERE',$glue);
        $conditions = array_values($wheres);
        return $this->query_fetch($sql, $conditions);
    }

    /**
     * @param $tableName
     * @param array $params
     * @param string $glue
     * @return bool|int
     */
    public function delete( $tableName,array $params = array(), $glue = 'AND') {
        $sql = "DELETE FROM"." " .$tableName;
        $sql = $this->to_sql($sql,$params, 'WHERE',$glue);
        $conditions = array_values($params);
        return $this->non_query_sql($sql, $conditions);
    }

    /**
     * @param string $sql
     * @param array $params
     * @return bool|int   受影响的行数
     */
    public function non_query_sql($sql, array $params)
    {
        $statement = $this->prepare_execute($sql, $params);
        if (false == $statement) {
            return false;
        }

        return $statement->rowCount();
    }

    public function update( $tableName, array $data, $where = array(), $glue = 'AND') {
        $sql =  " UPDATE $tableName ";
        $sql = $this->to_sql($sql,$data,'SET', ',');
        $sql = $this->to_sql($sql,$where,'WHERE',$glue);
        $insert_values = array_merge( array_values($data),array_values($where));
        return $this->non_query_sql($sql, $insert_values);
    }

    /**
     * 插入单条记录
     * @param $tableName
     * @param array $data
     * @return int
     */
    public function insert($tableName, array $data)
    {
        $tmp = array();
        $tmp[] = $data;
        return $this->inserts($tableName,$tmp);
    }

    /**
     * 插入多条记录
     * @param string $tableName
     * @param array $data 可以一次插入多个记录,记录的形式为二维数组
     * @return int 插入的行数
     */
    public function inserts($tableName, array $data)
    {

        $insert_values = array();
        $question_marks = array();
        foreach ($data as $d) {
            $question_marks[] = '(' . $this->_placeholders1('?', sizeof($d)) . ')';
            $insert_values = array_merge($insert_values, array_values($d));
        }
        $sql = "INSERT INTO $tableName (" . implode(",", array_keys($data[0])) . ") VALUES " . implode(
                ',',
                $question_marks
            );
        logger()->logInfo("inserts_sql：".$sql);
        return $this->non_query_sql($sql,$insert_values);
    }
///////////////////////////////////////////CALL to PDO///////////////////////////////////////////////////////////////////////

    /**
     * @param string $text
     * @param int $count
     * @param string $glue
     * @return string 由count个？组成的string
     */
    private function _placeholders1($text = '?', $count = 0, $glue = ",")
    {
        $result = array_fill(0, $count, $text);

        return implode($glue, $result);
    }

    /**
     * 插入多个BO（数组形式）到数据库
     * @param  $dos DataObject[]
     * @param string $action
     * @return int
     * @internal param string $acton
     */
    public function insertDOs( $dos, $action = "INSERT")
    {
        $arys = array();
        foreach ($dos as  $do) {
            assert(is_a($do, 'DataObject'));
            $arys[] = $do->get属性数组();
        }

        return $this->inserts($dos[0]->tableName(),$arys,$action);
    }

    function beginTransaction()
    {
        $this->pdo->beginTransaction();
    }

    function commit()
    {
        $this->pdo->commit();
    }

    function errorCode()
    {
        return $this->pdo->errorCode();
    }

    function errorInfo()
    {
        return $this->pdo->errorInfo();
    }

    function getAttribute( $attribute)
    {
        return $this->pdo->getAttribute($attribute);
    }

    function lastInsertId()
    {
        return $this->pdo->lastInsertId();
    }

    function query( $query)
    {
        return $this->pdo->query($query);
    }


    function quote( $string)
    {

        return $this->pdo->quote($string);
    }


    function rollBack()
    {
        $this->pdo->rollBack();
    }


    function setAttribute( $attribute, $mixed)
    {
        return $this->pdo->setAttribute($attribute, $mixed);
    }

}
